package legato.tasks
{
	import flash.events.Event;
	
	import mx.rpc.AbstractOperation;
	import mx.rpc.AbstractService;
	import mx.rpc.AsyncToken;
	import mx.rpc.events.FaultEvent;
	import mx.rpc.events.ResultEvent;
	
	[Event(name="result", type="mx.rpc.events.ResultEvent")]
	[Event(name="fault", type="mx.rpc.events.FaultEvent")]
	/**
	 * Simple task that executes async method. The <code>complete</code> event is dispatched after result or fault.  
	 * Set <code>operation</code> property or <code>service</code> and <code>operationName</code> properties.
	 * <code>service</code> and <code>operationName</code> properties are chcecked if <code>operation</code> property is not set.
	 * @author Piotr
	 * 
	 */
	public class ExecuteAsyncMethodTask extends AbstractTask
	{
		
		private var _params:Array;
		private var _service:AbstractService;
		private var _operationName:String;
		private var _operation:AbstractOperation;		

		private var token:AsyncToken = null;
		private var executeOperation:AbstractOperation = null;
	
		
	
		/**
		 * Method parameters. 
		 * @param val
		 * 
		 */
		public function get params():Array
		{
			return _params;
		}
	
		public function set params(val:Array):void
		{
			_params=val;
		}
		
		/**
		 * Service that contains the operation. Use with <code>operationName</code> property.  
		 * @return 
		 * 
		 */
		public function get service():AbstractService
		{
			return _service;
		}
	
		public function set service(val:AbstractService):void
		{
			_service=val;
		}
		
		/**
		 * Name of the operation of the <code>service</code>. The <code>service</code> property must be set. 
		 * @param val
		 * 
		 */
		public function set operationName(val:String):void
		{
			_operationName = val;
		}

		public function get operationName():String
		{
			return _operationName;
		}
		
		/**
		 * Operation to execute. If this is set <code>service</code> and 
		 * <code>operationName</code> properties are not chcecked.
		 * @param val
		 * 
		 */
		public function set operation(val:AbstractOperation):void
		{
			_operation = val;
		}

		public function get operation():AbstractOperation
		{
			return _operation;
		}
		
		/**
		 * True if task was executed and not completed or cancelled. 
		 * @return 
		 * 
		 */
		public function get isRunning():Boolean
		{
			return this.token != null;
		}
		
		/**
		 * Constructor 
		 * 
		 */
		public function ExecuteAsyncMethodTask()
		{
			super();
		}
		
		/**
		 * Creates new task for given operation. 
		 * @param operation operation to run
		 * @return new task
		 * 
		 */
		public static function createTaskForOperation(operation:AbstractOperation):ExecuteAsyncMethodTask
		{
			var newTask:ExecuteAsyncMethodTask = new ExecuteAsyncMethodTask();
			newTask.operation = operation;
			return newTask;	
		}
		
		/**
		 * Creates new task for given service and operation name 
		 * @param service 
		 * @param operationName
		 * @return new task
		 * 
		 */
		public static function createTaskForService(service:AbstractService, operationName:String):ExecuteAsyncMethodTask
		{
			var newTask:ExecuteAsyncMethodTask = new ExecuteAsyncMethodTask();
			newTask.service = service;
			newTask.operationName = operationName;
			return newTask;
		}
		
		/**
		 * Stops and cancels execution of async method. The <code>complete</code> event is dispatched immediately.
		 * Works only after execution and before completition. Otherwise nothing happens. 
		 */
		public function cancel():void
		{
			if (this.token != null && this.executeOperation != null)
			{
				this.executeOperation.cancel(this.token.message.messageId);
				this.token = null;
				this.dispatchEvent(new Event(Event.COMPLETE));
			}
		}
		
		public override function execute():void
		{
			if (this.token != null)
			{
				throw new Error("Cannot execute task. The task is running");
			}
			this.executeOperation = this.operation; 
			
			if (this.executeOperation == null)
			{
				if (this.service == null || this.operationName == null || this.operationName == "")
				{
					throw new Error("Set operation property or both service and operationName properties");
				}
				else
				{
					this.executeOperation = this.service.getOperation(this.operationName);
				}
			}
			
			this.executeOperation.addEventListener(FaultEvent.FAULT, this.faultHandler);
			this.executeOperation.addEventListener(ResultEvent.RESULT, this.resultHandler);
			this.executeOperation.arguments = this.params;
			this.token = executeOperation.send();
			
		}
		
		
		private function resultHandler(re:ResultEvent):void
		{
			this.executeOperation.removeEventListener(FaultEvent.FAULT, this.faultHandler);
			this.executeOperation.removeEventListener(ResultEvent.RESULT, this.resultHandler);
			this.dispatchEvent(re);
			this.token = null;
			this.dispatchEvent(new Event(Event.COMPLETE));
		}
		
		private function faultHandler(fe:FaultEvent):void
		{
			this.executeOperation.removeEventListener(FaultEvent.FAULT, this.faultHandler);
			this.executeOperation.removeEventListener(ResultEvent.RESULT, this.resultHandler);
			this.dispatchEvent(fe);
			this.token = null;
			this.dispatchEvent(new Event(Event.COMPLETE));
		}

	}
}